//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<redirect.cpp>
///	@path	~/xeres/src/lib/system/
///	@date	2007/12/15
///	@desc	Redirect stdio.

#include "config/config.h"

#include "lib/system/redirect.h"
#include "lib/system/lock.h"
#include "lib/system/signal.h"
#include "lib/system/thread.h"

#include <process.h>
#include <io.h>

namespace xeres {

#define	BUFFER_SIZE		(1024)

	namespace
	{
		/*!
			\class	_Redirect
			\brief	Internal class for file-pipe redirection.
		*/
		class _Redirect
		{
		public:

			// start
			bool start( const WString& pipename , FILE * target , function<void(const char*)> call )
			{
				LockGuard<ThreadLock> locker( m_lock );

				// if we have started redirection, close it first
				if( m_readPipe )
					do_close_pipe();

				// we create time randomized number
				srand( (uint)time( NULL ) );
				int timerand = rand();

				// add process id
				DWORD process_id = ::GetCurrentProcessId();

				wchar_t uid[64];
				swprintf( uid , _S("_%d_%i") , timerand , process_id );

				WString path( _S("\\\\.\\pipe\\") );
				path += pipename;
				path += uid;

				TRACE_INFO( _S("Openning pipe <%s> for redirection...") , path.c_str() );

				// create named pipe
				m_readPipe = ::CreateNamedPipe(
					path.c_str() ,
					FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_DUPLEX ,
					PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT ,
					1 ,
					BUFFER_SIZE ,
					BUFFER_SIZE ,
					NMPWAIT_WAIT_FOREVER ,
					NULL );

				if( m_readPipe == INVALID_HANDLE_VALUE )
				{
					m_readPipe = NULL;
					__win32_error( _S("_Redirect::start: Failed to create pipe: %s.") );
					return false;
				}

				if( WaitNamedPipe( path.c_str() , NMPWAIT_WAIT_FOREVER ) == FALSE )
				{
					__win32_error( _S("_Redirect::start: Failed to wait pipe: %s.") );
					CloseHandle( m_readPipe );
					return false;
				}

				// create write pipe
				m_file = _wfreopen( path.c_str() , _S("wb") , target );
				if( m_file == NULL )
				{
					__win32_error( _S("_Redirect::start: Failed to redirect pipe: %s.") );
					CloseHandle( m_readPipe );
					m_readPipe = NULL;
					return false;
				}

				// set null buffer for stdio
				setvbuf( target , NULL , _IONBF , 0 );

				m_call = call;

				// create serve thread
				m_serveThread = (HANDLE)
					_beginthreadex( NULL , 0 , _server_thread , this , 0 , NULL );
				::Sleep(0);

				// wait server connected
				::WaitForSingleObject( m_serverConnected , INFINITE );

				return true;
			}

			// end
			void stop( void )
			{
				LockGuard<ThreadLock> locker( m_lock );
				do_close_pipe();
			}

			// server thread
			static unsigned __stdcall _server_thread( void * data )
			{
				_Redirect * pThis = static_cast<_Redirect*>(data);

				DEBUG_RUN( ::SetThreadName( ::GetCurrentThreadId() , pThis->m_name ) );

				// connect write pipe
				BOOL bConnected = ::ConnectNamedPipe( pThis->m_readPipe , NULL )
					? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

				if( !bConnected )
				{
					__win32_error( _S("_Redirect::_serve_thread: Failed to connect pipe: %s") );
					FATAL_ABORT( _S("Failed to connect pipe") );
				}

				::SetEvent( pThis->m_serverConnected );

				// reading buffer
				ALIGN16 byte buffer[BUFFER_SIZE+64];

				// caching line buffer
				AString abuf;

				while( true )
				{
					DWORD bytesRead = 0;
					BOOL bSuccess = ::ReadFile(
						pThis->m_readPipe ,
						buffer ,
						BUFFER_SIZE * sizeof(byte) ,
						&bytesRead ,
						NULL );

// 					buffer[bytesRead] = 0;
// 					TRACE_INFO( _S("receive buf %i %s") , bytesRead , buffer );

					if( !bSuccess )
					{
						if( !pThis->m_close )
						{
							__win32_error( _S("Redirect STDIO error: %s") );
							continue;
						}
						break;
					}

					for( size_t i = 0 ; i < bytesRead ; ++i )
					{
						byte ch = buffer[i];
						if( ch == '\n' )
						{
							pThis->m_call( abuf.c_str() );
							abuf.clear();
						}
						else if( ch == '\r' )
						{
							continue;
						}
						else
						{
							abuf.push_back( ch );
						}
					}
				}
				return 0;
			}

			// do close
			void do_close_pipe( void )
			{
				m_close = 1;
				if( m_file )
				{
					fclose( m_file );
					m_file = NULL;
				}

				if( m_readPipe )
				{
					::CloseHandle( m_readPipe );
					m_readPipe = NULL;

					// wait,
					if( m_serveThread )
					{
						::WaitForSingleObject( m_serveThread , INFINITE );
						::CloseHandle( m_serveThread );
					}
					m_serveThread = NULL;
				}
				m_call = NULL;
				m_close = 0;
			}

			//@ ctor
			_Redirect( DEBUG_RUN( const char * name ) )
				: m_readPipe( NULL )
				, m_file( NULL )
				, m_serveThread( NULL )
				, m_call( NULL )
				, m_close( 0 )
			{
				DEBUG_RUN( m_name = name );
				m_serverConnected = ::CreateEvent( NULL , FALSE , FALSE , NULL );
			}

			//@ dtor
			~_Redirect( void )
			{
				stop();
				::CloseHandle( m_serverConnected );
			}

		//@ data
			ThreadLock	m_lock;

			HANDLE		m_readPipe;
			HANDLE		m_serverConnected;
			HANDLE		m_serveThread;

			FILE *		m_file;
			int			m_close;
			DEBUG_RUN( const char * m_name );

			function<void(const char*)> m_call;
		};

		/*!
			\class	_StdRedirect
			\brief	
		*/
		class _StdRedirect
		{
		public:

			// instance
			static _StdRedirect& instance( void )
			{
				static _StdRedirect s_StdRedirect;
				return s_StdRedirect;
			}

			// start
			bool start( const wchar_t * pipename ,
				function<void(const char*)> call_stdout ,
				function<void(const char*)> call_stderr )
			{
				WString name( pipename );
				// for stdout
				bool fstdout = m_stdout.start( name + _S("_stdout") , stdout , call_stdout );
				bool fstderr = m_stderr.start( name + _S("_stderr") , stderr , call_stderr );

				if( fstdout == NULL || fstderr == NULL )
				{
					//m_stdout.stop();
					m_stderr.stop();
					return false;
				}
				return true;
			}

			// stop
			void stop( void )
			{
				//m_stdout.stop();
				m_stderr.stop();
			}

		private:

			//@ ctor
			_StdRedirect( void )
				: m_stdout( DEBUG_RUN( "stdout" ) )
				, m_stderr( DEBUG_RUN( "stderr" ) )
			{
			}

			//@ dtor
			~_StdRedirect( void )
			{
			}

		//@ data

			_Redirect		m_stdout;
			_Redirect		m_stderr;
		};
	}

	bool redirect_stdio( const wchar_t * pipename ,
		function<void(const char*)> call_stdout ,
		function<void(const char*)> call_stderr )
	{
		return _StdRedirect::instance().start( pipename , call_stdout , call_stderr );
	}

	void reset_redirect( void )
	{
		_StdRedirect::instance().stop();
	}

} // namespace xeres
